﻿using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Data;
using System.Windows.Input;
using AnthStat.Infrastructure.Common;
using AnthStat.Domain;

namespace AnthStat.WPF.Applications
{
    [DebuggerDisplay("Name = {Name}, Mode = {DataCaptureMode}")]
    public sealed class ProjectViewModel : ViewModelBase
    {
        #region Members
        private bool _isShowingRemovePersonPrompt = false;
        private bool _isShowingNewPersonPrompt = false;
        private readonly DataCaptureMode _dataCaptureMode = DataCaptureMode.IndividualAssessments;
        private bool _isShowingProjectLoadScreen = false;
        private string _projectLoadStatus = String.Empty;
        private double _projectLoadProgress = 0.0;
        private readonly object _peopleLock = new object();
        private ICollectionView _childrenView;
        #endregion // Members

        #region Properties

        private ObservableCollection<PersonViewModel> PeopleCollection { get; set; }
        private Project Project { get; set; }

        /// <summary>
        /// Gets/sets whether the project's 'Loading' screen is being displayed
        /// </summary>
        public bool IsShowingProjectLoadScreen
        {
            get { return _isShowingProjectLoadScreen; }
            set
            {
                if (_isShowingProjectLoadScreen != value)
                {
                    _isShowingProjectLoadScreen = value;
                    RaisePropertyChanged("IsShowingProjectLoadScreen");
                }
            }
        }

        /// <summary>
        /// Gets/sets the status of the project as it is being loaded from disk
        /// </summary>
        public string ProjectLoadStatus
        {
            get { return _projectLoadStatus; }
            set
            {
                if (_projectLoadStatus != value)
                {
                    _projectLoadStatus = value;
                    RaisePropertyChanged("ProjectLoadStatus");
                }
            }
        }

        /// <summary>
        /// Gets/sets the progress being made to load the project from disk
        /// </summary>
        public double ProjectLoadProgress
        {
            get { return _projectLoadProgress; }
            set
            {
                if (_projectLoadProgress != value)
                {
                    _projectLoadProgress = value;
                    RaisePropertyChanged("ProjectLoadProgress");
                }
            }
        }

        /// <summary>
        /// Gets/sets whether the 'new person' control is being displayed to the user
        /// </summary>
        public bool IsShowingNewPersonPrompt
        {
            get { return _isShowingNewPersonPrompt; }
            set
            {
                _isShowingNewPersonPrompt = value;
                RaisePropertyChanged("IsShowingNewPersonPrompt");
            }
        }

        /// <summary>
        /// Gets/sets the author of the project
        /// </summary>
        public string Author
        {
            get { return Project.Author; }
            set
            {
                Project.Author = value;
                RaisePropertyChanged("Author");
            }
        }

        /// <summary>
        /// Gets/sets the indicator set used for children in this project
        /// </summary>
        public string IndicatorSet
        {
            get { return Project.IndicatorSet; }
            set
            {
                Project.IndicatorSet = value;
                RaisePropertyChanged("IndicatorSet");
            }
        }

        /// <summary>
        /// Gets the data capture mode for the project
        /// </summary>
        public DataCaptureMode DataCaptureMode
        {
            get { return _dataCaptureMode; }
        }

        /// <summary>
        /// Gets/sets the project's description
        /// </summary>
        public string Description
        {
            get { return Project.Description; }
            set
            {
                Project.Description = value;
                RaisePropertyChanged("Description");
            }
        }

        /// <summary>
        /// Gets whether or not the project has any validation errors on either itself or on objects owned by it (e.g. records)
        /// </summary>
        public override bool HasErrorsOnSelfOrOwnedObjects
        {
            get
            {
                if (HasErrors) return true;

                foreach (var pVm in PeopleCollection)
                {
                    if (pVm.HasErrorsOnSelfOrOwnedObjects)
                    {
                        return true;
                    }
                }

                return false;
            }
        }

        /// <summary>
        /// Gets/sets whether the prompt for removing a person from the database is being displayed
        /// </summary>
        public bool IsShowingRemovePersonPrompt
        {
            get { return _isShowingRemovePersonPrompt; }
            set
            {
                if (_isShowingRemovePersonPrompt != value)
                {
                    _isShowingRemovePersonPrompt = value;
                    RaisePropertyChanged("IsShowingRemovePersonPrompt");
                }
            }
        }

        /// <summary>
        /// Gets the collection view of children associated with this project
        /// </summary>
        public ICollectionView ChildrenView
        {
            get { return _childrenView; }
            private set
            {
                _childrenView = value;
                RaisePropertyChanged("ChildrenView");
            }
        }

        /// <summary>
        /// Gets/sets the name of the project
        /// </summary>
        public string Name
        {
            get 
            {
                if (Project == null) return String.Empty;
                return Project.Name; 
            }
            set
            {
                Project.Name = value;
                RaisePropertyChanged("Name");
            }
        }
        #endregion // Properties

        #region Constructors
        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="name">The name of the project</param>
        /// <param name="description">The description of the project</param>
        /// <param name="filePath">The file path to the project on disk</param>
        /// <param name="author">The author of the project</param>
        /// <param name="indicatorSet">The indicator set of the project</param>
        /// <param name="dataCaptureMode">The mode used to capture data in this project</param>
        public ProjectViewModel(string name, string description, string filePath, string author, string indicatorSet, DataCaptureMode dataCaptureMode)
        {
            // pre
            Contract.Requires(!String.IsNullOrEmpty(name));

            _dataCaptureMode = dataCaptureMode;
            PeopleCollection = new ObservableCollection<PersonViewModel>();

            System.Windows.Data.BindingOperations.EnableCollectionSynchronization(PeopleCollection, _peopleLock);

            IsShowingProjectLoadScreen = true;
            ProjectLoadStatus = String.Format("Loading project '{0}'...", name);
            ProjectLoadProgress = 0.0;

            Task.Factory.StartNew(
                () =>
                {
                    SetProject(name, description, filePath, _dataCaptureMode);
                    Author = author;
                    IndicatorSet = indicatorSet;
                    ProjectLoadStatus = "Populating records...";
                    PopulateCollections();
                },
                System.Threading.CancellationToken.None, TaskCreationOptions.LongRunning, TaskScheduler.Default).ContinueWith(
                delegate
                {
                    InitializeCollectionViews();
                    IsShowingProjectLoadScreen = false;
                    ProjectLoadStatus = String.Empty;
                    ProjectLoadProgress = 0.0;

                    ChildrenView = new ListCollectionView(PeopleCollection);// { Source = PeopleCollection }.View;
                    ChildrenView.Filter = p =>
                        ((PersonViewModel)p).IsMeasureable == true;

                }, TaskScheduler.FromCurrentSynchronizationContext());
        }

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="name">The name of the project</param>
        /// <param name="description">The description of the project</param>
        /// <param name="filePath">The file path to the project on disk</param>
        /// <param name="author">The author of the project</param>
        /// <param name="dataCaptureMode">The mode used to capture data in this project</param>
        public ProjectViewModel(string name, string description, string author, DataCaptureMode dataCaptureMode)
        {
            // pre
            Contract.Requires(!String.IsNullOrEmpty(name));

            // post
            Contract.Ensures(Project != null);
            Contract.Ensures(PeopleCollection != null);
            Contract.Ensures(ChildrenView != null);

            _dataCaptureMode = dataCaptureMode;
            PeopleCollection = new ObservableCollection<PersonViewModel>();
            SetProject(name, description, String.Empty, _dataCaptureMode);
            Author = author;
            IndicatorSet = "WHO2006";
            PopulateCollections();

            ChildrenView = new CollectionViewSource { Source = PeopleCollection }.View;
            ChildrenView.Filter = p =>
                ((PersonViewModel)p).IsMeasureable == true;

            RaisePropertyChanged("ChildrenView");
        }
        #endregion Constructors

        #region Methods

        private void InitializeCollectionViews()
        {
            foreach (PersonViewModel pVm in PeopleCollection)
            {
                pVm.InitializeCollectionViews();
            }
        }

        private void SetProject(string name, string description, string filePath, DataCaptureMode dataCaptureMode)
        {
            // pre
            Contract.Requires(!String.IsNullOrEmpty(name));

            // post
            Contract.Ensures(Project != null);

            if (String.IsNullOrEmpty(filePath))
            {
                Project = new Project(name, description, dataCaptureMode);
            }
            else
            {
                Project = new Project(name, description, filePath, dataCaptureMode);
            }
            RaisePropertyChanged("Name");
        }

        private void PopulateCollections()
        {
            // pre
            Contract.Requires(Project != null);

            // post
            Contract.Ensures(PeopleCollection != null);

            double inc = 100.0 / Project.People.Count;

            foreach (var personVm in Project.People.Select(person => new PersonViewModel(person)))
            {
                lock (_peopleLock)
                {
                    PeopleCollection.Add(personVm);
                }
                ProjectLoadProgress += inc;
            }
        }

        /// <summary>
        /// Closes the project
        /// </summary>
        public void Close()
        {
            PeopleCollection.Clear();
        }

        /// <summary>
        /// Sets the location of the project's file on disk
        /// </summary>
        /// <param name="filePath">The full name and path to the project's file on disk</param>
        public void SetFileLocation(string filePath)
        {
            // pre
            Contract.Requires(!String.IsNullOrEmpty(filePath));
            Contract.Requires(filePath.EndsWith(".apj", StringComparison.OrdinalIgnoreCase));

            Project.SetFileLocation(filePath);
        }
        #endregion // Methods

        #region Commands

        public ICommand SaveCommand { get { return new RelayCommand(SaveCommandExecute, CanExecuteSaveCommand); } }
        private void SaveCommandExecute()
        {
            string rawXml = Project.Save();

            System.Xml.Linq.XDocument doc = System.Xml.Linq.XDocument.Parse(rawXml);
            doc.Save(Project.LocationOnDisk);
        }

        public ICommand AddPersonCommand { get { return new RelayCommand(AddPersonCommandExecute, CanExecuteAddPersonCommand); } }
        private void AddPersonCommandExecute()
        {
            int maxId = PeopleCollection.Count > 0 ? PeopleCollection.Max(x => x.NumericId) : 0;

            // add the measurement to the domain model
            var p = new Person(Guid.NewGuid().ToString(), DateTime.Now, ++maxId, Project)
            {
                IsMeasureable = true
            };

            Project.AddPerson(p);

            var pVm = new PersonViewModel(p);
            pVm.InitializeCollectionViews();

            PeopleCollection.Add(pVm);
            ChildrenView.Refresh();
            ChildrenView.MoveCurrentTo(pVm);

            IsShowingNewPersonPrompt = true;

            if (DataCaptureMode == Domain.DataCaptureMode.NutritionalSurvey)
            {
                if (pVm.AddMeasurementCommand.CanExecute(null))
                {
                    pVm.AddMeasurementCommand.Execute(null);
                }
            }
        }

        public ICommand CloseAddPersonPromptCommand { get { return new RelayCommand(CloseAddPersonPromptExecute, CanExecuteCloseAddPersonPromptCommand); } }
        private void CloseAddPersonPromptExecute()
        {
            IsShowingNewPersonPrompt = false;
        }

        private bool CanExecuteCloseAddPersonPromptCommand()
        {
            if (ChildrenView == null) return false;

            var pVm = ChildrenView.CurrentItem as PersonViewModel;
            if (pVm != null)
            {
                if (!String.IsNullOrEmpty(pVm.Sex) && pVm.DateBirth.HasValue)
                {
                    return true;
                }
                else
                {
                    return false;
                }
            }
            else
            {
                return false;
            }
        }

        public ICommand RemovePersonCommand { get { return new RelayCommand(RemovePersonCommandExecute, CanExecuteRemovePersonCommand); } }
        private void RemovePersonCommandExecute()
        {
            var pVm = ChildrenView.CurrentItem as PersonViewModel;
            if (pVm != null)
            {
                PeopleCollection.Remove(pVm);

                ChildrenView.Refresh();

                Project.RemovePerson(new Guid(pVm.GlobalRecordId));
            }
            if (IsShowingRemovePersonPrompt)
            {
                IsShowingRemovePersonPrompt = false;
            }
            if (IsShowingNewPersonPrompt)
            {
                IsShowingNewPersonPrompt = false;
            }
        }

        public ICommand PromptRemovePersonCommand { get { return new RelayCommand(PromptRemovePersonCommandExecute, CanExecuteRemovePersonCommand); } }
        private void PromptRemovePersonCommandExecute()
        {
            IsShowingRemovePersonPrompt = !IsShowingRemovePersonPrompt;
        }

        public ICommand SearchChildrenCommand { get { return new RelayCommand<string>(SearchChildrenCommandExecute); } }
        private void SearchChildrenCommandExecute(string searchText)
        {
            if (String.IsNullOrEmpty((searchText)))
            {
                ChildrenView.Filter = p =>
                    ((PersonViewModel) p).IsMeasureable == true;
            }
            else
            {
                ChildrenView.Filter = p => 
                (
                    ((PersonViewModel) p).IsMeasureable == true &&
                    ((PersonViewModel) p).FullName.Contains(searchText, StringComparison.OrdinalIgnoreCase)
                );
            }
        }

        private bool CanExecuteSaveCommand()
        {
            if (!CanExecuteAddPersonCommand()) return false;
            return true;
        }

        private bool CanExecuteAddPersonCommand()
        {
            if (this.HasErrors) return false;

            if (ChildrenView != null && ChildrenView.CurrentItem != null)
            {
                var cVm = ChildrenView.CurrentItem as PersonViewModel;

                if (cVm != null && !cVm.HasErrors && !cVm.HasMeasurementErrors)
                {
                    return true;
                }
                else
                {
                    return false;
                }
            }

            return true;
        }

        private bool CanExecuteRemovePersonCommand()
        {
            if (ChildrenView != null && ChildrenView.CurrentItem != null)
            {
                var cVm = ChildrenView.CurrentItem as PersonViewModel;

                if (cVm != null && !cVm.HasErrors && !cVm.HasMeasurementErrors)
                {
                    return true;
                }
                else
                {
                    return false;
                }
            }
            return false;
        }

        #endregion // Commands
    }
}
